package com.booter.mvel;

import lombok.extern.slf4j.Slf4j;
import org.mvel2.MVEL;
import org.mvel2.ParserContext;
import org.mvel2.PropertyAccessException;
import org.mvel2.UnresolveablePropertyException;
import org.mvel2.integration.Interceptor;
import org.mvel2.integration.VariableResolver;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author lcw
 * @date 2021/7/15 15:09
 */
@Slf4j
public class MvelScriptEvaluator {

    private List<String> methodNames = new ArrayList<>();
    /**
     * Runtime context
     */
    private static ParserContext context = new ParserContext();
    private static CustomMapVariableResolverFactory functionFactory = new CustomMapVariableResolverFactory();

    //    static {
    //        context.addPackageImport("java.util.Date");
    //        context.addImport(Date.class);
    //    }


    public void addFunction(Class<? extends FunctionDelegate> cls) {
        Method[] mth = cls.getDeclaredMethods();
        try {
            for (Method method : mth) {
                if (!Modifier.isStatic(method.getModifiers()) && !Modifier.isPublic(method.getModifiers())) {
                    continue;
                }
                String key = cls.getSimpleName().concat("-").concat(method.getName());
                if (methodNames.contains(key)) {
                    continue;
                }
                context.addImport(method.getName(), method);
                methodNames.add(key);
            }
        } catch (Exception ex) {
            log.error("加载函数[{}]异常", cls.getSimpleName(), ex);
            throw new RuntimeException("加载函数[" + cls.getSimpleName() + "]异常", ex);
        }
    }


    /**
     * 加载脚本函数
     *
     * @param def 函数脚本内容
     */
    public void addScriptFunction(String def) {
        MVEL.eval(def, functionFactory);
    }

    /**
     * @param fun 函数名称
     * @param def 函数定义
     */
    public void replaceScriptFunction(String fun, String def) {
        CustomMapVariableResolverFactory factory = new CustomMapVariableResolverFactory();
        MVEL.eval(def, factory);
        VariableResolver variableResolver = factory.getVariableResolver(fun);
        Object variables = factory.getVariables(fun);
        functionFactory.replace(fun, variables, variableResolver);
    }


    public void addInterceptor(String name, Interceptor interceptor) {
        Map<String, Interceptor> re = context.getInterceptors();
        if (re == null || re.isEmpty()) {
            re = new HashMap<>();
        }
        re.put(name, interceptor);
        context.setInterceptors(re);
    }


    public void addFunction(Class<? extends FunctionDelegate> cls, Method method) {
        if (!Modifier.isStatic(method.getModifiers()) || !Modifier.isPublic(method.getModifiers())) {
            return;
        }
        String key = cls.getSimpleName().concat("-").concat(method.getName());
        if (methodNames.contains(key)) {
            return;
        }
        try {
            context.addImport(method.getName(), MVEL.getStaticMethod(cls, method.getName(), method.getParameterTypes()));
            methodNames.add(key);
        } catch (Exception ex) {
            log.error("加载静态函数[{}]异常", cls.getSimpleName(), ex);
            throw new RuntimeException("加载静态函数[" + cls.getSimpleName() + "]异常", ex);
        }
    }

    /**
     * Runtime 添加函数支持
     * 1 类中的方法必须为静态函数，否则不会被加载
     * 2 方法名必须唯一，后续同名方法不会被加载
     *
     * @param cls 函数实现类
     */
    public void addStaticFunction(Class<? extends FunctionDelegate> cls) {
        Method[] mth = cls.getDeclaredMethods();
        try {
            for (Method method : mth) {
                if (!Modifier.isStatic(method.getModifiers()) || !Modifier.isPublic(method.getModifiers())) {
                    continue;
                }
                String key = cls.getSimpleName().concat("-").concat(method.getName());
                if (methodNames.contains(key)) {
                    continue;
                }
                context.addImport(method.getName(), MVEL.getStaticMethod(cls, method.getName(), method.getParameterTypes()));
                methodNames.add(key);
            }
        } catch (Exception ex) {
            log.error("加载静态函数[{}]异常", cls.getSimpleName(), ex);
            throw new RuntimeException("加载静态函数[" + cls.getSimpleName() + "]异常", ex);
        }
    }

    /**
     * 非编译执行,用于表达式验证
     *
     * @param expression 表达式
     * @param params     map参数列表
     * @param <T>        结果类型
     * @return 表达式计算结果
     */
    public <T> T eval(String expression, Map<String, Object> params) {
        try {
            return (T) MVEL.eval(expression, context, params);
        } catch (Exception ex) {
            log.error("表达式[{}]执行异常", expression, ex);
            throw new RuntimeException("加载静态函数[" + expression + "]异常", ex);
        }
    }


    public Serializable compile(String expression) {
        try {
            return MVEL.compileExpression(expression, context);
        } catch (Exception ex) {
            log.error("编译表达式[{}]异常,msg:{}", expression, ex.getMessage(), ex);
            throw new RuntimeException("编译表达式[" + expression + "]异常", ex);
        }
    }

    public Serializable compileV2(String expression) {
        return MVEL.compileExpression(expression, context);
    }

    /**
     * 执行策略表达式
     *
     * @param expression 编译后表达式
     * @param params     参数
     * @param <T>        返回值类型
     * @return 表达式计算结果
     */
    public <T> T run(Serializable expression, Map<String, Object> params) {
        try {
            return (T) MVEL.executeExpression(expression, params, functionFactory);
        } catch (PropertyAccessException ex) {
            log.warn("表达式参数缺失,params:{},msg:{}", params, ex.getMessage());
            return null;
        } catch (UnresolveablePropertyException ex) {
            log.warn("表达式属性缺失,params:{},msg:{}", params, ex.getMessage());
            return null;
        } catch (NullPointerException ex) {//NOPMD
            log.warn("表达式属性缺失,params:{},msg:{}", params, ex.getMessage());
            return null;
        } catch (Exception ex) {
            log.error("表达式执行异常,params:{},msg:{}", params, ex.getMessage(), ex);
            throw new RuntimeException("表达式执行异常", ex);
        }
    }

}
